/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.webos.java.base;

import org.webos.java.root.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import org.urchr.web.db.DBSession;
import org.webos.java.base.Application;
import org.webos.java.base.ApplicationType;
import org.webos.java.base.db.apps.DBApplication;

class ApplicationRecord{
    String path;
    Class<Application> applicationClass;
}

/**
 *
 * @author kostya
 */
public class ApplicationManager {

    static String appContextPath = "";

    private static Map<String, ApplicationRecord> applications = new TreeMap<String, ApplicationRecord>();

    public static boolean reloadApplications(String aAppsFolder, String aCacheFolder){
        DBSession dbs = new DBSession();
        try {
            List<DBApplication> apps = dbs.createQuery("from DBApplication where active=true order by name").list();
            applications.clear();
            int i = 0;
            for (DBApplication app : apps) {
                Class _class = Class.forName(app.getClassName());
                if(_class!=null){
                    if(refreshApplication(aAppsFolder+"/"+app.getPath(), aCacheFolder, app.getCode(), i==0)){
                        ApplicationRecord rec = new ApplicationRecord();
                        rec.applicationClass = _class;
                        rec.path = app.getPath();
                        applications.put(app.getCode(), rec);
                    }
                }
                i++;
            }
            Log.info(null, "Loaded applications = "+applications.size());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }finally{
            dbs.close();
        }
    };

    public static Class<? extends Application> getApplication(String aCode){
        ApplicationRecord rec = applications.get(aCode);
        return rec!=null?rec.applicationClass:null;
    }

    public static ApplicationType getApplicationInfo(String aCode){
        ApplicationRecord rec = applications.get(aCode);
        if(rec!=null){
            return rec.applicationClass.getAnnotation(ApplicationType.class);
        }
        return null;
    }

    public static String getApplicationPath(String aCode){
        ApplicationRecord rec = applications.get(aCode);
        return rec!=null?rec.path:null;
    }

    public static Set<String> getApplications(){
        return applications.keySet();
    }

    public static String loadApplication(File aFolder){
        DBSession dbs = new DBSession();
        try {
            dbs.begin();
            String className = null;
            File manifest = new File(aFolder.getPath()+"/manifest.mf");
//            Log.debug(null, "Manifest:", manifest.exists(), manifest.isDirectory());
            if(manifest.exists() && !manifest.isDirectory()){
                Properties p = new Properties();
                p.load(new FileInputStream(manifest));
                className = Utils.trim(p.getProperty("Main-Class"), null);
            }
            if(className!=null){
                Class _class = Class.forName(className);
                ApplicationType at = (ApplicationType) _class.getAnnotation(ApplicationType.class);
                if(at!=null && Application.class.isAssignableFrom(_class)){
                    DBApplication dBApplication = (DBApplication) dbs.createQuery("from DBApplication where code=:code").
                            setString("code", at.code()).uniqueResult();
                    if(dBApplication==null)
                        dBApplication = new DBApplication();
                    dBApplication.setClassName(className);
                    dBApplication.setCode(at.code());
                    dBApplication.setDescription(at.description());
                    dBApplication.setPath(aFolder.getName());
                    dBApplication.setVersion(at.version());
                    dBApplication.setName(at.name());
                    dBApplication.setSystemType(at.systemType());
                    dBApplication.setActive(true);
                    dbs.save(dBApplication);
                    dbs.commit();
                    return at.code();
                }
            }            
            dbs.rollback();
        } catch (Throwable t) {
            dbs.rollback();
            Log.error(null, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        } finally {
            dbs.close();
        }
        return null;
    }

    public static boolean loadApplications(String aFolder){
        DBSession dbs = new DBSession();
        try {
            File dir = new File(aFolder);
            File[] apps = dir.listFiles();
            List<String> codes = new ArrayList<String>();
            List<DBApplication> q = dbs.createQuery("from DBApplication").list();
            for (DBApplication a : q) {
                codes.add(a.getCode());
            }
            if(apps==null)
                return true;
//            Log.debug(null, "Applications total: "+apps.length);
            for (int i = 0; i < apps.length; i++) {
                File app = apps[i];
                if(app.getName().startsWith("."))
                    continue;
                if(app.isDirectory()){
                    String code = loadApplication(app);
                    if(code!=null)
                        codes.remove(code);
                }else{
                    //JAR file
                }
            }
            dbs.begin();
            for (String code : codes) {
                DBApplication dba = (DBApplication) dbs.createQuery("from DBApplication where code=:code").
                        setString("code", code).uniqueResult();
                if(dba!=null){
                    dba.setActive(false);
                    dbs.save(dba);
                }
            }
            dbs.commit();
            return true;
        } catch (Throwable t) {
            dbs.rollback();
            Log.error(null, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
            return false;
        } finally {
            dbs.close();
        }
    }
/*
            applications.clear();
            File dir = new File(aFolder);
            File[] apps = dir.listFiles();
            if(apps==null)
                return true;
//            Log.debug(null, "Applications total: "+apps.length);
            for (int i = 0; i < apps.length; i++) {
                File app = apps[i];
                if(app.isDirectory() && !app.getName().startsWith(".")){
                    String className = loadApplication(app.getCanonicalPath(), aCacheFolder, app.getName());
                    if(className!=null){
                        try {
                            Class _class = Class.forName(className);
                            if(_class!=null && Application.class.isAssignableFrom(_class)){
                                applications.put(app.getName(), (Class<Application>)_class);
                            }
                        } catch (Throwable t) {
                            Log.error(null, "Exception thrown: " + t.getMessage());
                            t.printStackTrace();
                        }
                    }
                }else{
                    //JAR file
                }
            }
            Log.info(null, "Loaded applications = "+applications.size());

 */
    private static void mergeFiles(File[] aFiles, String[] defines, Writer aWriter){
        List<File> files = new ArrayList();
        for (int i = 0; i < aFiles.length; i++) {
            File file = aFiles[i];
            if(!file.isDirectory())
                files.add(file);
        }
        Collections.sort(files, new Comparator<File>() {
            public int compare(File arg0, File arg1) {
                return arg0.getName().compareTo(arg1.getName());
            }
        });
        int linesTotal = 0;
        for (int i = 0; i < files.size(); i++) {
            File file = files.get(i);
            try {
                BufferedReader reader =
                        new BufferedReader(new InputStreamReader(new FileInputStream(file), "utf-8"));
                String line = null;
                Stack<Boolean> stack = new Stack();
                boolean skipMode = false;
                while((line = reader.readLine())!=null){
                    line = Utils.trim(line);
                    if(line.startsWith("//#ifdef")){
                        String def = Utils.trim(line.substring(8));
                        stack.push(skipMode);
                        if(!skipMode){
                            skipMode = true;
                            for (int j = 0; j < defines.length; j++) {
                                String define = defines[j];
                                if(define.equalsIgnoreCase(def)){
                                    skipMode = false;
                                    break;
                                }
                            }
                        }
                    }else{
                        if(line.startsWith("//#endif")){
                            if(!stack.empty())
                                skipMode = stack.pop();
                        }else{
                            if(!skipMode){
                                if(line.contains("url('/")){
                                    line = line.replace("url('", "url('"+appContextPath);
                                }
                                aWriter.write(line+"\n");
                                linesTotal++;
                            }
                        }
                    }
                }
            } catch (Throwable t) {
                Log.error(null, "Exception thrown: " + t.getMessage());
                t.printStackTrace();
            }
        }
        
//        Log.info(null, "Lines written: "+linesTotal);
    }

    private static boolean refreshCache(String aFolder, String aCacheFolder, String aAppName, String aSuffix, boolean aAppend){
        try {
            Log.debug(null, "Refresh files in folder "+aFolder+" to "+aCacheFolder, aAppName, aSuffix);
            File folder = new File(aFolder);
            File[] files = folder.listFiles();
            if(files==null)
                return true;
            for (int i = 0; i < Constants.PLATFORMS.length; i++) {
                String platform = Constants.PLATFORMS[i];
                String fileName = "/"+aAppName+"_"+platform+aSuffix;
                BufferedWriter writer =
                        new BufferedWriter(new OutputStreamWriter(
                        new FileOutputStream(aCacheFolder+fileName, aAppend), "utf-8"));
                mergeFiles(files, new String[]{platform}, writer);
                writer.close();
            }
            return true;
        } catch (Throwable t) {
            Log.error(null, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
            return false;
        }
    }

    public static boolean refreshApplication(String aFolder, String aCacheFolder, String aAppName, boolean aFirst){
        Log.debug(null, "Refresh app: "+aAppName+", folder: "+aFolder);
        try {
            if(!refreshCache(aFolder+Constants.JS_APP_PATH, aCacheFolder, aAppName, ".js", false))
                return false;
            if(!refreshCache(aFolder+Constants.CSS_APP_PATH, aCacheFolder, "all", ".css", aFirst?false:true))
                return false;
            return true;
        } catch (Throwable t) {
            Log.error(null, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
            return false;
        }
    }
}
